package BNS.plumbing;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.ObjectInputStream;
import java.io.PrintStream;
import java.rmi.RemoteException;
import java.util.Vector;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;

/**
 * This class is involved with registering clients and players. It carries out
 * the mechanics of starting and stopping experiments (aka games). It is not
 * involved in the actual execution of graph play dynamics.
 */
public class MovieHost implements Runnable {
  protected Vector<ClientLiaison> clients;
  protected BNSGameController game;
  protected WaitingGame waitgame;
  protected Supervisor supervisor;

  enum Action {
    nada, quit, inst
  };

  private Action action;

  protected PrintStream sessionLog;
  private static int nextClientID = 1001;
  private static String fs = "/"; // File.separator inside all jar files
  private static String selectedExp, automaticExp;

  /** Sets up a Supervisor panel, initializes the Library, etc. */
  public MovieHost() {
    clients = new Vector<ClientLiaison>(100);
    supervisor = new Supervisor(this);
    supervisor.setVisible(true);
    sessionLog = System.err;
    try {
      waitgame = new WaitingGame();
    } catch (RemoteException e) {
      System.err.println(e);
    }
    action = Action.nada;
    new Thread(this).start();
    System.err.println("MovieHost ready for clients");
  }

  public void installGame(BNSGameController gc, SimuDriver mgr) {
    assert (gc != null);
    if (game != null) {
      System.err.println("You have a game in progress!");
      return;
    } // TODO: accommodate multiple games at a time
    int gameSize = gc.getSize();
    System.err.println("game " + gc.getTitle() + " needs " + gameSize + " players");
    PrintStream gameLog = System.out;
    System.err.println("will init " + gc.getClass());
    gc.init(mgr, null, sessionLog, gameLog);
    game = gc;
    // mgr.begin();
    // game.gameLog.println("installGame");
    // mgr.assignClients(gc, gc.grpk.graph.enumNodes()); // gc.loadGame();
  }

  /** The simulation driver calls this method to create robot clients. */
  public synchronized ClientLiaison register(int clientID) {
    // sessionLog.println(" registering robot as "+clientID);
    ClientLiaison client = null;
    synchronized (clients) {
      try {
        client = new ClientLiaison("robot" + clientID, clientID);
      } catch (RemoteException e) {
        System.err.println(e);
      }
      client.adoptGame((BNSGameController) waitgame, null);
      clients.add(client);
      if (clientID >= nextClientID)
        nextClientID = clientID + 1;
    }
    // supervisor.dataChange();
    return client;
  }

  /**
   * The method to invoke in order to play a movie. It is expected that this
   * will be made into a jar file and be invoked by double-clicking. Hence there
   * are no command-line args.
   * 
   * A list of .data files will be found in a file called "gameData-ls" at the
   * top level directory in the jar file. Each line in /gameData-ls is the name
   * of a file in /gameData/ . (I could not find a way to get the names of these
   * files from the directory itself.) Each of the .data files should have a
   * 'game' line in it that names a .game file, also to be found in /gameData/ .
   */
  public static void main(String[] args) {
    System.err.println("MovieHost started");
    MovieHost host = new MovieHost();
    System.err.println("created an instance of " + host.getClass());
    automaticExp = System.getProperty("GAME");
    if (automaticExp != null)
      host.install(automaticExp);
  }

  private void install(String game) {
    System.err.println("installing initial game " + game);
    selectedExp = game;
    action = Action.inst;
    synchronized (this) {
      this.notify();
    }
  }

  private void getGameData(String dataFile) throws InterruptedException, RemoteException, FileNotFoundException, IOException, ClassNotFoundException, Exception {
    System.err.println("getGameData " + dataFile);
    String gDdF = fs + "gameData" + fs + dataFile;
    InputStream is = MovieHost.class.getResourceAsStream(gDdF);
    if (is == null)
      throw (new Exception("Failed to get " + gDdF));
    LineNumberReader gameData = new LineNumberReader(new InputStreamReader(is));
    gameData.mark(40000);
    String line;
    do {
      line = gameData.readLine();
    } while (!line.startsWith("game "));
    // String gameFile= line.substring(5);
    String gameFile = line.replaceAll("game /home/ian/software/", "");
    System.err.println("gameFile is " + gameFile);
    String gDgF = fs + "gameData" + fs + gameFile;
    is = MovieHost.class.getResourceAsStream(gDgF);
    if (is == null)
      throw (new Exception("cannot find " + gDgF + " in jar."));
    ObjectInputStream os = new ObjectInputStream(is);
    BNSGameController gc = Library.castGame(os.readObject());
    if (gc == null)
      throw (new Exception("could not load game"));
    System.err.println("gameController read");
    gc.setSource(new File(dataFile));
    gameData.reset();
    SimuDriver mgr = new SimuDriver(this, gc, gameData);
    if (mgr == null)
      throw (new Exception("could not create SimuDriver"));
    System.err.println("simulation Data read");
    installGame(gc, mgr);
    game.gameLog.println("game " + gameFile);
  }

  public void run() {
    Thread.yield();
    while (action != Action.quit) {
      Thread.yield();
      if (action == Action.inst) {
        System.err.println("run will install " + selectedExp + ".");
        try {
          getGameData(selectedExp);
        } catch (Exception e) {
          e.printStackTrace();
          JOptionPane.showMessageDialog(null, "problem with game data: " + e, "alert", JOptionPane.ERROR_MESSAGE);
        }
      }
      // System.err.println("action "+action+" done");
      action = Action.nada;
      synchronized (this) {
        try {
          wait();
        } catch (InterruptedException e) {
          System.err.println(e);
        }
      }
    }
    System.err.println("shutting down MovieHost");
    System.exit(0);
  }

  public void gameOver() {
    game = null;
    if (automaticExp != null) {
      action = Action.quit;
      synchronized (this) {
        this.notify();
      }
    }
  }

  /** Implements a GUI for selecting and starting the playback of experiments */
  public class Supervisor extends JFrame implements ActionListener {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private MovieHost host;
    private JPanel cPanel;
    private JButton inst, quit;
    private AbstractTableModel dataModel;
    private Vector<String> expFiles;
    private JTable table;

    public Supervisor(MovieHost h) {
      host = h;
      setTitle("BNS Movie Maven");
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      cPanel = new JPanel(); // TODO: remove more
      inst = new JButton("install");
      cPanel.add(inst);
      inst.addActionListener(this);
      quit = new JButton("quit");
      cPanel.add(quit);
      quit.addActionListener(this);
      getContentPane().add(BorderLayout.NORTH, cPanel);
      expFiles = getDataFiles();
      dataModel = new AbstractTableModel() {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        public int getColumnCount() {
          return 1;
        }

        public int getRowCount() {
          return expFiles.size();
        }

        public Object getValueAt(int row, int col) {
          if (row >= expFiles.size())
            return null;
          return expFiles.elementAt(row);
        }

        public String getColumnName(int col) {
          return "data file";
        }
      };
      table = new JTable(dataModel);
      // TableColumn tc= table.getColumnModel().getColumn(0);
      // tc.setMinWidth(40); tc.setMaxWidth(500); tc.setPreferredWidth(300);
      JScrollPane scrollpane = new JScrollPane(table);
      getContentPane().add(BorderLayout.SOUTH, scrollpane);
      setSize(300, 850);
      validate();
      pack();
      setLocation(820, 20);
      // setLocationByPlatform(true);
      setVisible(true);
      System.err.println("MovieHost Supervisor ready");
    }

    private Vector<String> getDataFiles() {
      Vector<String> names = new Vector<String>(20);
      try {
        System.err.println("starting gameData search");
        InputStream is = MovieHost.class.getResourceAsStream("/gameData-ls");
        if (is == null)
          System.err.println("Failed to get gameData dir");
        LineNumberReader gameData = new LineNumberReader(new InputStreamReader(is));
        System.err.println("reading gameData list");
        String line;
        while ((line = gameData.readLine()) != null) {
          names.add(line);
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
      System.err.println("listing gameData list " + names.size());
      for (String name : names) {
        System.err.println("game file name: " + name);
      }
      return names;
    }

    /** mandated by the ActionListener interface; handles various input widgets */
    public void actionPerformed(ActionEvent e) {
      Object src = e.getSource();
      //System.err.println("Supervisor action event "+src);
      if (src == inst) {
        if (table.getSelectedRowCount() == 1) {
          int choice = table.getSelectedRow();
          selectedExp = expFiles.elementAt(choice);
        } else
          selectedExp = "";
        action = Action.inst;
      }
      if (src == quit)
        action = Action.quit;
      synchronized (host) {
        host.notify();
      }
    }

    /** called to prod the GUI to refresh its display. */
    //		public void dataChange() { dataModel.fireTableDataChanged(); }
  } // EOClass Supervisor

} // EOC MovieHost
